/******************************************************************************/
//	FSM.cpp	-	Provides functionality to transition between states
/******************************************************************************/

#include "FSM.h"
#include "eventfactory.h"
#include "FSMScript.h"
#include "InputManager.h"

FSM::FSM(){}
FSM::~FSM(){Shutdown();}

// defines
#define TRANSITION_FREQUENCY	500		// frequency of transitions (500ms = 0.5s)

void	FSM::Init()
{

}

void	FSM::Initialize(CActor *pActor)
{
	// Set transition timer (frequency at which the FSM can transition)
	m_transitionTimer.Set(TRANSITION_FREQUENCY);

	/*
		TODO:	Create instances of all possible states and store them in the appropriate container.
				Set the initial starting state to be StateIdle and the Next state to be NULL.

			!!! IMPORTANT !!! - States MUST be pushed onto the container in the following order:

						1) StateIdle
						2) StateBlock
						3) StateFight
	*/
	CStateIdle *StateIdle = new CStateIdle(pActor);
	CStateBlock *StateBlock = new CStateBlock(pActor);
	CStateFight *StateFight = new CStateFight(pActor);
	SetInitialStates(StateIdle);

	PushBackState(StateIdle);
	PushBackState(StateBlock);
	PushBackState(StateFight);

}

void FSM::SetInitialStates(CState * pStateStart, CState * pStateNext )
{
	// TODO: Set the FSM's states to the states specified and Enter the initial state.
	m_pStateCurrent = pStateStart;
	m_pStateNext = pStateNext;
	m_pStateCurrent->Enter();
}

bool FSM::Update()
{
	/*
		TODO:	Check input from the keyboard.  Use the input to create events and enqueue them into the state machine.
				Note: Do not create New Events here, use the event factory for the creation of events.

				Implement Pausing by using the bit mask inside of the FSM.
				Transition only if the FSM is not in the paused state
				Note: Pausing is not a toggling operation, so 'P' pauses but does not unpause, unpause is handled in EnQueueEvent()

	*/

	// Here's an example of how to use the inputManager to check for keypresses:
	// inputManager.IsKeyDownBuffered(DIK_A)

	
	if(inputManager.IsKeyDownBuffered(DIK_A))
	{
		Event *newEvent = eventFactory.GetEvent();
		newEvent->Set(newEvent->FSM_INPUT_A);
		if(!EnQueueEvent(newEvent))
			eventFactory.ReturnEvent(newEvent);
	}
	if(inputManager.IsKeyDownBuffered(DIK_B))
	{
		Event *newEvent = eventFactory.GetEvent();
		newEvent->Set(newEvent->FSM_INPUT_B);
		if(!EnQueueEvent(newEvent))
			eventFactory.ReturnEvent(newEvent);
	}
	if(inputManager.IsKeyDownBuffered(DIK_C))
	{
		Event *newEvent = eventFactory.GetEvent();
		newEvent->Set(newEvent->FSM_INPUT_C);
		if(!EnQueueEvent(newEvent))
			eventFactory.ReturnEvent(newEvent);
	}
	if(inputManager.IsKeyDownBuffered(DIK_D))
	{
		Event *newEvent = eventFactory.GetEvent();
		newEvent->Set(newEvent->FSM_INPUT_D);	
		if(!EnQueueEvent(newEvent))
			eventFactory.ReturnEvent(newEvent);
	}
	if(inputManager.IsKeyDownBuffered(DIK_P))
	{
		AddFlags(this->MASK_PAUSED);	
	}
	if(!AreFlagsSet(this->MASK_PAUSED))
	{
		m_pStateCurrent->Update();
		Transition();
	}
	else 
		return false;
	// Upon success...
	return true;
}

void	FSM::Shutdown()
{
	// TODO: Perform necessary clean up. 
	//       Think about shutdown from the FSM object's point of view. Not the application's.
	//		 what does the FSM object know about? The shutdown function does not guarantee the object
	//		 is pulled from existence. In other words: If I ran this function twice in a row would it cause an
	//		 issue? 

	//		 Also think about consistency in how we have been transitioning. When we transition we enter then exit,
	//		 what was the last thing we did with our current state?
	int sizetest = m_eventQueue.size();
	std::list<Event*,std::allocator<Event*>>::iterator eventIter = m_eventQueue.begin();
	for(;eventIter != m_eventQueue.end();eventIter++)
	{
		eventFactory.ReturnEvent(*eventIter);
	}
	m_eventQueue.clear();
	ClearTransitionTable();
	for(unsigned i = 0; i < m_states.size();i++)
		delete m_states[i];
	m_states.clear();

}



void FSM::Transition()
{
	// Check if it is time to transition
	if(m_transitionTimer.Check())
	{
		/*	
			TODO:	Transition from The current state into another state (possibly re-entering the same state)
					which is defined by the Transition Table (m_table) Note: make sure you check the header file to see how this is declared

					Things to consider: Transition is a process, not simply a pointer assignment operation. 
					What if the event queue is empty? What if there is no next state to go to?
		*/

		if(m_eventQueue.size() > 0)
		{
			m_pStateNext = m_table[m_pStateCurrent->GetId()][m_eventQueue.front()->GetId()];
			eventFactory.ReturnEvent(m_eventQueue.front());
			m_eventQueue.pop_front();
			
		}
		if(m_pStateNext)
		{
			m_pStateCurrent->Exit();
			m_pStateCurrent = m_pStateNext;
			m_pStateCurrent->Enter();
			m_pStateNext = NULL;
		}



		// Reset Timer
		m_transitionTimer.Set(TRANSITION_FREQUENCY);
	}
}

bool FSM::EnQueueEvent(Event* pEvent)
{

	/*
		TODO:
			This function should queue events "relevant" to the FSM (See Event header file for valid events)
			It should also unpause the state machine if the event is relevant, ie valid.

			The idea here is to write a relevancy check such that you could add new events with new id's and not 
			have to update the code in this function. ( less coding in the future == good).
	*/
	if(!(pEvent->GetId() <= pEvent->EVENT_ID_UNDEFINED) && !(pEvent->GetId() >= pEvent->EVENT_ID_LAST))
	{
		RemoveFlags(this->MASK_PAUSED);
		m_eventQueue.push_back(pEvent);
		return true;
	}
	// If event not understood, send to the	(super class) and see
	// if they have the ability to handle the Event.
	return CObjectBase::EnQueueEvent(pEvent);
}

void FSM::ClearTransitionTable()
{
	m_table.clear();
}

int FSM::GetEventQueueSize()
{
	return (int)m_eventQueue.size();
}

void FSM::PushBackState(CState * pStateIn)
{
	if(!pStateIn)
		return;
	m_states.push_back(pStateIn);
}	
